package com.sooth.system.auth.controller;

import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.sooth.common.core.constant.Constants;
import com.sooth.common.core.constant.GlobalConstants;
import com.sooth.common.core.domain.R;
import com.sooth.common.core.domain.model.LoginBody;
import com.sooth.common.core.exception.ServiceException;
import com.sooth.common.core.utils.SpringUtils;
import com.sooth.common.core.utils.StringUtils;
import com.sooth.common.core.utils.reflect.ReflectUtils;
import com.sooth.common.redis.utils.RedisUtils;
import com.sooth.common.web.config.properties.CaptchaProperties;
import com.sooth.common.web.enums.CaptchaType;
import com.sooth.system.auth.service.IAuthStrategy;
import com.sooth.system.auth.service.SysLoginService;
import com.sooth.system.auth.vo.CaptchaVo;
import com.sooth.system.auth.vo.LoginVo;
import com.sooth.system.client.service.ISysClientService;
import com.sooth.system.client.view.SysClientVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.time.Duration;

/**
 * 认证
 *
 * @author Lion Li
 */
@Slf4j
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {

    private final SysLoginService loginService;
    private final ISysClientService clientService;
    private final CaptchaProperties captchaProperties;


    /**
     * 登录方法
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @PostMapping("/login")
    public R<LoginVo> login(@RequestBody LoginBody loginBody) {
        String status = "0";
        String msg = "登录成功";
        try {
            String clientKey = loginBody.getClientKey();
            if (StrUtil.isEmpty(clientKey)) {
                throw new ServiceException("客户端公钥不能为空");
            }
            // 授权类型和客户端id
            String clientId = loginBody.getClientId();
            String uuid = loginBody.getUuid();
            String grantType = loginBody.getGrantType();
            validateCaptcha(loginBody);
            SysClientVo client = clientService.queryByClientKey(clientKey);
            if (client == null) {
                log.info("客户端公钥:{}不存在", clientKey);
                throw new ServiceException("客户端公钥：" + clientKey + "不存在");
            }
            String validClientId = SecureUtil.md5(uuid + client.getClientSecret());
            // 查询不到 client 或 client 内不包含 grantType
            if (!validClientId.equals(clientId)) {
                log.info("客户端id: {} 认证类型：{} 异常!.", clientId, grantType);
                throw new ServiceException("客户端认证异常");
            }
            // 校验租户
            loginService.checkTenant(loginBody.getTenantId());
            // 登录
            LoginVo loginVo = IAuthStrategy.login(loginBody, client);
            loginService.recordLoginInfo(loginBody.getUserName());
            return R.ok(loginVo);
        } catch (Exception e) {
            status = "1";
            msg = e.getMessage();
            return R.fail(msg);
        } finally {
            loginService.recordLoginLog(loginBody.getTenantId(), loginBody.getUserName(), status, msg);
        }
    }

    /**
     * 退出登录
     */
    @PostMapping("/logout")
    public R<Void> logout() {
        loginService.logout();
        return R.ok();
    }

    /**
     * 生成验证码
     */
    @GetMapping("/code")
    public R<CaptchaVo> getCode() {
        CaptchaVo captchaVo = new CaptchaVo();
        boolean captchaEnabled = captchaProperties.getEnable();
        String uuid = IdUtil.simpleUUID();
        captchaVo.setUuid(uuid);
        // 保存验证码信息
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
        String code = "";
        if (captchaEnabled) {
            // 生成验证码
            CaptchaType captchaType = captchaProperties.getType();
            boolean isMath = CaptchaType.MATH == captchaType;
            Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
            CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
            AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
            captcha.setGenerator(codeGenerator);
            captcha.createCode();
            code = captcha.getCode();
            if (isMath) {
                ExpressionParser parser = new SpelExpressionParser();
                Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
                code = exp.getValue(String.class);
            }
            captchaVo.setImg(captcha.getImageBase64());
        } else {
            captchaVo.setCaptchaEnabled(false);
        }
        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
        return R.ok(captchaVo);
    }

    private void validateCaptcha(LoginBody loginBody) {
        String uuid = loginBody.getUuid();
        String code = loginBody.getCode();
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            throw new ServiceException("验证码已失效");
        }
        if (captchaProperties.getEnable() && !code.equalsIgnoreCase(captcha)) {
            throw new ServiceException("验证码错误");
        }
    }
}
